/**
 * Copyright (C) 2008 Christopher Allen Ogden
 *
 *     This file is part of Odysi.
 *
 *  Odysi is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Odysi is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Odysi.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "socket.h"
#include <sys/socket.h>
#include <fstream>
#include <errno.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <stdexcept>
#include <string.h>
#include "std.h"

SocketList Socket::_list;

Socket::Socket(int socket)
{
    inc(socket);
}

Socket::~Socket()
{
    dec();
}

Socket & Socket::operator=(const Socket &rhs)
{
    // Make sure it isn't a self-assignment.
    if (this == &rhs) return *this;

    // Don't bother doing anything if they're already the same.
    if (rhs._socket == _socket) return *this;

    dec();
    inc(rhs._socket);

    return *this;
}

Socket::operator int(void) const
{
    return _socket;
}

Socket::Socket(const Socket &rhs)
{
    inc(rhs._socket);
}

Socket::Socket(void)
{
    inc(socket(AF_INET, SOCK_STREAM, 0));
}

void Socket::inc(int socket)
{
    _socket = socket;
    if (_list.find(_socket) == _list.end()) {
        _list[_socket] = 1;
    } else {
        _list[_socket]++;
    }
}

void Socket::dec(void)
{
    if (--_list[_socket] == 0) {
        _list.erase(_socket);

        // Don't bother closing if it's not a valid socket.
        if (_socket != SOCKET_ERROR) {
            if (close(_socket) == SOCKET_ERROR) {
                perror("Could not close socket");
            }
        }
    }
}

void Socket::setReuseAddress(void)
{
    int on = 1;
    if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
        throwError("Could not set to reuse address");
    }
}

void Socket::setNonblocking(void)
{
    int on = 1;
    if (ioctl(_socket, FIONBIO, (char *)&on) == -1) {
        throwError("Could not set to non-blocking");
    }
}

void Socket::bind(int port)
{
    struct sockaddr_in address;
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons(port);
    address.sin_family = AF_INET;
    if (::bind(_socket, (struct sockaddr*)&address, sizeof(address)) == -1) {
        throwError("Could not bind");
    }
}

int Socket::accept(void)
{
    int nAddressSize = sizeof(struct sockaddr_in);
    struct sockaddr_in address;
    int ret;
    if ((ret = ::accept(_socket, (struct sockaddr*)&address, (socklen_t*)&nAddressSize)) == -1) {
        if (ret != EWOULDBLOCK && errno != EAGAIN) throwError("Could not accept");
    }
    return ret;
}

void Socket::listen(int queue)
{
    if (::listen(_socket, queue) == -1) {
        throwError("Could not listen");
    }
}

void Socket::connect(std::string host, int port)
{
    struct hostent *server = gethostbyname(host.c_str());
    if (server == NULL) {
        throwError("Could not find host");
    }
    struct sockaddr_in address;
    bzero((char *)&address, sizeof(address));
    bcopy((char *)server->h_addr, (char *)&address.sin_addr.s_addr, server->h_length);
    address.sin_port = htons(port);
    address.sin_family = AF_INET;
    if (::connect(_socket, (struct sockaddr*)&address, sizeof(address)) == -1) {
        throwError("Could not connect");
    }
}

int Socket::send(const void * const data, int length)
{
    int ret;
    if ((ret = ::send(_socket, data, length, 0)) == -1) {
        if (ret != EWOULDBLOCK && errno != EAGAIN) throwError("Could not send");
    }
    return ret;
}

int Socket::recv(void * data, int length)
{
    int ret;
    if ((ret = ::recv(_socket, data, length, 0)) == -1) {
        if (ret != EWOULDBLOCK && errno != EAGAIN) throwError("Could not receive");
    }
    if (ret == 0) {
        throw std::runtime_error("Connection closed.");
    }
    return ret;
}

void Socket::throwError(std::string message)
{
    throw std::runtime_error(message + ": " + strerror(errno) + ". (" + toString(errno) + ")");
}
